#ifndef GB_H
#define GB_H

#define GBC_FREQ    8400000
#define GB_FREQ     4194304

#define PIXELX		160
#define PIXELY		144

#define DEFAULT_FPS 60

#define ZERO_FL		0x80	// Zero flag
#define ZERO_FL_BIT 7
#define SUB_FL		0x40	// Add/Sub flag
#define SUB_FL_BIT  6
#define HC_FL		0x20	// Half Carry flag
#define HC_FL_BIT   5
#define C_FL		0x10	// Carry flag
#define C_FL_BIT    4

#define DOUBLE_REG	0xffff
#define REG_MAX		0xff
#define HALF_REG	0x0f

enum MODE {
    GB,
    GBC
};

// This represents external hardware that
//  may be present in the cartridge
// ROM address 0x147 contains a one-byte number
//  that specifies external hardware. The enum
//  values correspond to this number
enum EXT_HARD {
    ROM_ONLY            = 0x00,
    MBC1                = 0x01,
    MBC1_RAM            = 0x02,
    MBC1_RAM_BATT       = 0x03,
    MBC2                = 0x05,
    MBC2_BATT           = 0x06,
    ROM_RAM             = 0x08,
    ROM_RAM_BATT        = 0x09,
    MMM01               = 0x0B,
    MMM01_RAM           = 0x0C,
    MMM01_RAM_BATT      = 0x0D,
    MBC3_TIM_BATT       = 0x0F,
    MBC3_TIM_RAM_BATT   = 0x10,
    MBC3                = 0x11,
    MBC3_RAM            = 0x12,
    MBC3_RAM_BATT       = 0x13,
    MBC4                = 0x15,
    MBC4_RAM            = 0x16,
    MBC4_RAM_BATT       = 0x17,
    MBC5                = 0x19,
    MBC5_RAM            = 0x1A,
    MBC5_RAM_BATT       = 0x1B,
    MBC5_RUMB           = 0x1C,
    MBC5_RUMB_RAM       = 0x1D,
    MBC5_RUMB_RAM_BATT  = 0x1E,
    CAM                 = 0xFC,
    TAMA5               = 0xFD,
    HuC3                = 0xFE,
    HuC1_RAM_BATT       = 0xFF
};

// The values in this struct is the union
//  of the hardware sets for all memory bank
//  controllers i.e. some MBCs might not use
//  everything here
typedef struct {
    // reg0 isn't required
    unsigned char reg1;
    // reg2 is implicit in rom_offset/eram_offset
    unsigned char reg3;

    // Real Time Clock
    struct {
        unsigned char s;        // Seconds
        unsigned char m;        // Minutes
        unsigned char h;        // Hours
        unsigned char d_low;    // Days Low
        unsigned char d_high;   // Days high
    } RTC;

} MBC;

// This struct represents GPU state
typedef struct
{
    unsigned fps;
    // The following three values should only
    //  be modified by GUI.c
	unsigned char *screen;	// Screen image buffer
	int screenw;
	int screenh;

	int cycles;             // Cycle count for LCD mode control
    int frame_ready;        // Set when the last line of a frame
                            //  has been sent to screen buffer

    unsigned char LCDC;     // LCD Control register
    unsigned char STAT;     // LCDC Status

    unsigned char SCY;      // Scroll Y
    unsigned char SCX;      // Scroll X
    unsigned char LY;       // LCDC Y-coordinate
    unsigned char LYC;      // LCDC Compare
    unsigned char WX;       // Window X position plus 7
    unsigned char WY;       // Window Y position

    // Non-color GB only
    unsigned char BGP;      // BackGround Pallette data
    unsigned char OBP0;     // Object Palette 0 data
    unsigned char OBP1;     // Object Palette 1 data

    // Color GB only below here
    unsigned char BGPI;     // BackGround Palette Index
    unsigned char BGPD;     // BackGround Palette Data
    unsigned char OBPI;     // Object (sprite) Palette Index
    unsigned char OBPD;     // Object (sprite) Palette Data

    unsigned char VBK;      // VRAM Bank

    unsigned char HDMA1;    // new DMA source, high
    unsigned char HDMA2;    // new DMA source, low
    unsigned char HDMA3;    // new DMA destination, high
    unsigned char HDMA4;    // new DMA destination, low
    unsigned char HDMA5;    // new DMA length/mode/start

} gb_gpu;

// This struct represents the entire gameboy state
typedef struct
{
    enum MODE mode;
    unsigned long long cpu_freq;

    gb_gpu gpu_state;

	int A;		// Accumulator
	int F;		// Flags
	int B;		// B
	int C;		// C
	int D;		// D
	int E;		// E
	int H;		// H
	int L;		// L
	int SP;		// Stack Pointer
	int PC;		// Program Counter

	unsigned char P1;		// Joypad
    unsigned char TIMA;		// Timer Counter
    int TIMA_INC;			// When this hits 0, increment TIMA
    unsigned char TMA;      // Timer Modulo
    unsigned char TAC;      // Timer Control
	unsigned char IME;	    // Interrupt Master Enable
	unsigned char IE;	    // Interrupt Enable
	unsigned char IF;		// Interrupt Flag

	unsigned char *vram;	// Video RAM
#define VRAM_START  0x8000
#define VRAM_SIZE   0x2000
// Offsets within VRAM
#define TILE1       0x0
#define TILE0       0x800
#define TILEMAP0    0x1800
#define TILEMAP1    0x1C00

	unsigned char *eram;		// External (cartridge) RAM
	int eram_offset;            // Calculated via mbc registers
#define ERAM_START  0xA000
#define ERAM_SIZE   0x2000

	unsigned char *wram;		// Working RAM
#define WRAM_START  0xC000
#define WRAM_SIZE   0x2000

	unsigned char *hram;		// High RAM
#define HRAM_START  0xFF80
#define HRAM_SIZE   0x7F

	unsigned char *rom;

	unsigned char *oam;			// Sprite Attribute Table
#define OAM_START   0xFE00
#define OAM_SIZE    0xA0

    enum EXT_HARD ext;
    unsigned eram_size;           // Size of external RAM
    // May not be used, depending on
    //  the value of ext
    MBC mbc;
    int rom_offset;             // Calculated via mbc registers

} gb_state;

int gb_cpu_execute(gb_state *gb, int cycles);

void gb_init(gb_state *gb, char *fileName);

void gb_reset_cpu(gb_state *gb);

void gb_gpu_update(gb_state *gb, int cycles);

unsigned char gb_read_mem(gb_state *gb, int address);

void gb_write_mem(gb_state *gb, int address, unsigned char value);

void gb_check_input(gb_state *gb);

#endif
